//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjNinjaSync.h"
#include "NjPulseEvent.h"

//----------------------------------------------------------------------------
#if NJ_PULSE_EVENT_TRACKING_ENABLED
#	include "NjDebugLocklessCircularBuffer.h"

	class NjPulseEvent::TrackerEntry
	{
	public:
		NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(TrackerEntry);
		NJ_TRIVIAL_DESTRUCTOR(TrackerEntry);

		NjUint m_uThreadDebugId;
		NjUint m_uLine;
		NjPulseEvent const * m_pPulseEvent;

		static const NjUint BUFFER_SIZE = 1024;

		NJ_DISABLE_COPY(TrackerEntry);
	};
	//----------------------------------------------------------------------------
	class NjPulseEvent::Tracker
	{
	public:
		static void Record(NjPulseEvent const * pPulseEvent, NjUint uThreadDebugId, NjUint uLine);

	private:
		typedef NjDebugLocklessCircularBuffer<NjPulseEvent::TrackerEntry, NjPulseEvent::TrackerEntry::BUFFER_SIZE> CircularBuffer;
		static CircularBuffer ms_kCircularBuffer;

		NJ_DISABLE_INSTANCE(Tracker);
	};

#	define NJ_PULSE_EVENT_TRACKING() Tracker::Record(this, uThreadDebugId, __LINE__)
#else // NJ_PULSE_EVENT_TRACKING_ENABLED
#	define NJ_PULSE_EVENT_TRACKING()
#endif // NJ_PULSE_EVENT_TRACKING_ENABLED
//----------------------------------------------------------------------------
#if NJ_PULSE_EVENT_STORE_USERS_ENABLED
#	define NJ_PULSE_EVENT_USER_ENTER() UserEnter()
#	define NJ_PULSE_EVENT_USER_LEAVE() UserLeave()
#else // NJ_PULSE_EVENT_STORE_USERS_ENABLED
#	define NJ_PULSE_EVENT_USER_ENTER()
#	define NJ_PULSE_EVENT_USER_LEAVE()
#endif // NJ_PULSE_EVENT_STORE_USERS_ENABLED
//----------------------------------------------------------------------------
NjPulseEvent::~NjPulseEvent()
{
	NJ_COMPILE_ASSERT((STATE_WAITING_THREADS_MASK/STATE_WAITING_THREADS_ONE)==(STATE_WAKEN_THREADS_MASK/STATE_WAKEN_THREADS_ONE));

#if NJ_PULSE_EVENT_STORE_USERS_ENABLED
	NJ_ASSERT(m_kUsers.Get()==0);
#endif // NJ_PULSE_EVENT_STORE_USERS_ENABLED

	Event * const pEvent(m_kEvent.Get());

	if (pEvent==NJ_NULL)
		return;

	NjEventsSystem::DestroyEvent(pEvent);
}
//----------------------------------------------------------------------------
void NjPulseEvent::WakeOne()
{
	NJ_PULSE_EVENT_USER_ENTER();

#if NJ_PULSE_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_PULSE_EVENT_TRACKING_ENABLED

	NJ_PULSE_EVENT_TRACKING();

	NjUint uState(m_kState.Get());

	for(;;)
	{
		NjUint const uCurrentWaiting((uState&STATE_WAITING_THREADS_MASK)*(STATE_WAKEN_THREADS_ONE/STATE_WAITING_THREADS_ONE));
		NjUint const uCurrentWaken(uState&STATE_WAKEN_THREADS_MASK);

		NJ_ASSERT(uCurrentWaiting>=uCurrentWaken);

		if (uCurrentWaiting<=uCurrentWaken)
		{
			NJ_PULSE_EVENT_TRACKING();
			NJ_PULSE_EVENT_USER_LEAVE();
			return;
		}

		NjUint const uNewState(uState+STATE_WAKEN_THREADS_ONE);
		NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

		if (uOldState==uState)
			break;

		uState=uOldState;
	}

	if ((uState&STATE_EVENT_UNSET)!=0)
	{
		Event * pEvent(m_kEvent.Get());

		NJ_ASSERT(pEvent!=NJ_NULL);

		for(;;)
		{
#if NJ_ASSERT_ENABLED
			NjBool const bResult=
#endif // NJ_ASSERT_ENABLED

			NjEventsSystem::SetEvent(pEvent);

			NJ_ASSERT(bResult);

			NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE-STATE_EVENT_UNSET);
			NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

			if (uOldState==uState ||
				(uOldState&STATE_EVENT_UNSET)==0)
				break;

			uState=uOldState;
		}
	}

	NJ_PULSE_EVENT_TRACKING();
	NJ_PULSE_EVENT_USER_LEAVE();
}
//----------------------------------------------------------------------------
void NjPulseEvent::WakeAll()
{
	NJ_PULSE_EVENT_USER_ENTER();

#if NJ_PULSE_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_PULSE_EVENT_TRACKING_ENABLED

	NJ_PULSE_EVENT_TRACKING();

	NjUint uState(m_kState.Get());

	for(;;)
	{
		NjUint const uCurrentWaiting((uState&STATE_WAITING_THREADS_MASK)*(STATE_WAKEN_THREADS_ONE/STATE_WAITING_THREADS_ONE));
		NjUint const uCurrentWaken(uState&STATE_WAKEN_THREADS_MASK);

		NJ_ASSERT(uCurrentWaiting>=uCurrentWaken);

		if (uCurrentWaiting==uCurrentWaken)
		{
			NJ_PULSE_EVENT_TRACKING();
			NJ_PULSE_EVENT_USER_LEAVE();
			return;
		}

		NjUint const uNewState((uState&(~STATE_WAKEN_THREADS_MASK))+uCurrentWaiting);
		NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

		if (uOldState==uState)
			break;

		uState=uOldState;
	}

	if ((uState&STATE_EVENT_UNSET)!=0)
	{
		Event * pEvent(m_kEvent.Get());

		NJ_ASSERT(pEvent!=NJ_NULL);

		for(;;)
		{
#if NJ_ASSERT_ENABLED
			NjBool const bResult=
#endif // NJ_ASSERT_ENABLED

			NjEventsSystem::SetEvent(pEvent);

			NJ_ASSERT(bResult);

			NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE-STATE_EVENT_UNSET);
			NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

			if (uOldState==uState ||
				(uOldState&STATE_EVENT_UNSET)==0)
				break;

			uState=uOldState;
		}
	}

	NJ_PULSE_EVENT_TRACKING();
	NJ_PULSE_EVENT_USER_LEAVE();
}
//----------------------------------------------------------------------------
NjBool NjPulseEvent::TryWait(NjUint uTimeMs)
{
	NJ_PULSE_EVENT_USER_ENTER();

#if NJ_PULSE_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_PULSE_EVENT_TRACKING_ENABLED

	NJ_PULSE_EVENT_TRACKING();

	NjUint uSpinCount(NJ_MAX_UINT);
	TimeMs uStart(0);

	NjUint uState(m_kState.PreAdd(STATE_WAITING_THREADS_ONE));

	for(;;)
	{
		NJ_ASSERT((uState&STATE_WAITING_THREADS_MASK)!=0);

		NjUint uWakenThreads;

		for(;;)
		{
			uWakenThreads=(uState&STATE_WAKEN_THREADS_MASK);
			if (uWakenThreads<=0)
				break;

			NjUint const uNewState(uState-STATE_WAKEN_THREADS_ONE-STATE_WAITING_THREADS_ONE);
			NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

			if (uOldState==uState)
				break;

			uState=uOldState;
		}

		if (uWakenThreads>0)
			break;

		if (uTimeMs==0)
		{
			RemoveWaitingThread();
			NJ_PULSE_EVENT_TRACKING();
			NJ_PULSE_EVENT_USER_LEAVE();
			return NJ_FALSE;
		}

		if (uSpinCount==NJ_MAX_UINT)
		{
			if (!NjEventsSystem::GetTimeMs(uStart))
			{
				NJ_BREAK();
				RemoveWaitingThread();
				NJ_PULSE_EVENT_TRACKING();
				NJ_PULSE_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			if (!NjEventsSystem::GetSpinCount(uSpinCount))
			{
				NJ_BREAK();
				RemoveWaitingThread();
				NJ_PULSE_EVENT_TRACKING();
				NJ_PULSE_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			NJ_ASSERT(uSpinCount>0);
			--uSpinCount;
		}

		if (uSpinCount>0)
		{
			NjUint uIterations(uSpinCount);

			do
			{
				uState=m_kState.Get();
				for(;;)
				{
					uWakenThreads=(uState&STATE_WAKEN_THREADS_MASK);
					if (uWakenThreads<=0)
						break;

					NjUint const uNewState(uState-STATE_WAKEN_THREADS_ONE-STATE_WAITING_THREADS_ONE);
					NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

					if (uOldState==uState)
						break;

					uState=uOldState;
				}

				if (uWakenThreads>0)
					break;

				--uIterations;
			}
			while (uIterations>0);

			if (uIterations>0)
				break;
		}

		Event * pEvent(m_kEvent.Get());

		if (pEvent==NJ_NULL)
		{
			NJ_ASSERT((uState&STATE_EVENT_UNSET)==0);
			pEvent=NjEventsSystem::CreateEvent(NJ_FALSE);

			if (pEvent==NJ_NULL)
			{
				NJ_BREAK();
				RemoveWaitingThread();
				NJ_PULSE_EVENT_TRACKING();
				NJ_PULSE_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			Event * const pOldEvent(m_kEvent.PostCompareExchange(NJ_NULL, pEvent));
			if (pOldEvent!=NJ_NULL)
			{
				if (!NjEventsSystem::DestroyEvent(pEvent))
				{
					NJ_BREAK();
					RemoveWaitingThread();
					NJ_PULSE_EVENT_TRACKING();
					NJ_PULSE_EVENT_USER_LEAVE();
					return NJ_FALSE;
				}

				pEvent=pOldEvent;
			}
			else
			{
				NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE+STATE_EVENT_UNSET);
				NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

				if (uOldState==uState)
					uState|=STATE_EVENT_UNSET;
				else
					uState=uOldState;
			}
		}

		if ((uState&STATE_EVENT_UNSET)==0)
		{
			for(;;)
			{
				if (!NjEventsSystem::UnsetEvent(pEvent))
				{
					NJ_BREAK();
					RemoveWaitingThread();
					NJ_PULSE_EVENT_TRACKING();
					NJ_PULSE_EVENT_USER_LEAVE();
					return NJ_FALSE;
				}

				uWakenThreads=(uState&STATE_WAKEN_THREADS_MASK);
				NjUint uNewState(uState+STATE_ABA_COUNTER_ONE+STATE_EVENT_UNSET);
				if (uWakenThreads>0)
					uNewState-=STATE_WAKEN_THREADS_ONE+STATE_WAITING_THREADS_ONE;

				NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

				if (uOldState==uState)
					break;

				uState=uOldState;

				if ((uOldState&STATE_EVENT_UNSET)!=0)
				{
					uWakenThreads=0;
					break;
				}
			}

			if (uWakenThreads>0)
				break;
		}

		NjEventsSystem::TryResult const eTryResult(NjEventsSystem::TryWaitEvent(pEvent, uTimeMs));

		if (eTryResult!=NjEventsSystem::TRY_SUCCESS)
		{
			NJ_ASSERT(eTryResult==NjEventsSystem::TRY_TIMEOUT && uTimeMs!=NjEventsSystem::WAIT_FOREVER);
			RemoveWaitingThread();
			NJ_PULSE_EVENT_TRACKING();
			NJ_PULSE_EVENT_USER_LEAVE();
			return NJ_FALSE;
		}

		if (uTimeMs!=NjEventsSystem::WAIT_FOREVER)
		{
			TimeMs uEnd(0);

			if (!NjEventsSystem::GetTimeMs(uEnd))
			{
				NJ_BREAK();
				RemoveWaitingThread();
				NJ_PULSE_EVENT_TRACKING();
				NJ_PULSE_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			NJ_ASSERT(uEnd>=uStart);
			uEnd-=uStart;
			uStart+=uEnd;

			if (uEnd>=uTimeMs)
				uTimeMs=0;
			else
				uTimeMs-=uEnd;
		}

		uState=m_kState.Get();
	}

	NJ_PULSE_EVENT_TRACKING();
	NJ_PULSE_EVENT_USER_LEAVE();
	return NJ_TRUE;
}
//----------------------------------------------------------------------------
void NjPulseEvent::RemoveWaitingThread()
{
	NjUint uState(m_kState.Get());

	for(;;)
	{
		NjUint uNewState(uState-STATE_WAITING_THREADS_ONE);

		NjUint const uCurrentWaiting((uState&STATE_WAITING_THREADS_MASK)*(STATE_WAKEN_THREADS_ONE/STATE_WAITING_THREADS_ONE));
		NjUint const uCurrentWaken(uState&STATE_WAKEN_THREADS_MASK);

		NJ_ASSERT(uCurrentWaiting>=uCurrentWaken && uCurrentWaiting>0);

		if (uCurrentWaiting<=uCurrentWaken)
			uNewState-=STATE_WAKEN_THREADS_ONE;

		NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

		if (uOldState==uState)
			break;

		uState=uOldState;
	}
}
//----------------------------------------------------------------------------
#if NJ_PULSE_EVENT_STORE_USERS_ENABLED
	inline void NjPulseEvent::UserEnter()
	{
#	if NJ_ASSERT_ENABLED
		NjUint uTotUsers=
#	endif // NJ_ASSERT_ENABLED

		m_kUsers.PreIncrement();

		NJ_ASSERT(uTotUsers>0);
	}
	//----------------------------------------------------------------------------
	inline void NjPulseEvent::UserLeave()
	{
#	if NJ_ASSERT_ENABLED
		NjUint uTotUsers=
#	endif // NJ_ASSERT_ENABLED

		m_kUsers.PostDecrement();

		NJ_ASSERT(uTotUsers>0);
	}
#endif // NJ_PULSE_EVENT_STORE_USERS_ENABLED
//----------------------------------------------------------------------------
#if NJ_PULSE_EVENT_TRACKING_ENABLED
	void NjPulseEvent::Tracker::Record(NjPulseEvent const * pPulseEvent, NjUint uThreadDebugId, NjUint uLine)
	{
		TrackerEntry * const pEntry(ms_kCircularBuffer.Record());

		pEntry->m_pPulseEvent=pPulseEvent;
		pEntry->m_uThreadDebugId=uThreadDebugId;
		pEntry->m_uLine=uLine;
	}
	//----------------------------------------------------------------------------
	NjPulseEvent::Tracker::CircularBuffer NjPulseEvent::Tracker::ms_kCircularBuffer;
#endif // NJ_PULSE_EVENT_TRACKING_ENABLED
//----------------------------------------------------------------------------
